﻿using OpenCvSharp;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace LunarSF.SHomeWorkshop.LunarMarkdownEditor
{

    public class 答题卡定义
    {
        public 答题卡定义(string difinitionSourceText)
        {
            _答题卡定义源文本 = difinitionSourceText;

            解析答题卡定义源文本();
        }

        private double? _答题卡宽高比;
        /// <summary>
        /// 这是用来防止读卡时找错外框（不能完全防止）。
        /// </summary>
        public double? 答题卡宽高比 { get => _答题卡宽高比; }

        private double? _边框宽度比 = null;
        /// <summary>
        /// 答题卡边框占答题卡总宽度的比（用于读卡时切边）。
        /// </summary>
        public double? 边框宽度比 { get => _边框宽度比; }


        private double? _边框高度比 = null;
        /// <summary>
        /// 答题卡边框占答题卡总高度的比（用于读卡时切边）。
        /// </summary>
        public double? 边框高度比 { get => _边框高度比; }


        private int? _答题卡列数 = null;

        public int? 答题卡列数
        {
            get => _答题卡列数;
        }

        private int? _答题卡行数 = null;

        public int? 答题卡行数
        {
            get => _答题卡行数;
        }

        private string _答题卡定义源文本;
        /// <summary>
        /// 答题卡定义源文本。
        /// </summary>
        public string 答题卡定义源文本 { get => _答题卡定义源文本; }

        private List<答题卡编号填涂条目> _编号条目列表 = new List<答题卡编号填涂条目>();
        public List<答题卡编号填涂条目> 编号条目列表 { get => _编号条目列表; }

        private List<答题卡试题填涂条目> _试题条目列表 = new List<答题卡试题填涂条目>();
        public List<答题卡试题填涂条目> 试题条目列表 { get => _试题条目列表; }
        public bool 精确匹配模式 { get; internal set; }
        public bool 用冒号分隔答案 { get; internal set; }
        public bool 是否带批改信息 { get; internal set; }
        public string 参考答案字符串 { get; internal set; }
        public List<AnswerSheetFile> 待处理文件列表 { get; internal set; }

        public double 编号最小匹配比例 { get; internal set; }
        public double 试题最小匹配比例 { get; internal set; }

        public void 解析答题卡定义源文本()
        {
            _编号条目列表.Clear();
            _试题条目列表.Clear();

            if (string.IsNullOrEmpty(_答题卡定义源文本)) return;

            var lines = _答题卡定义源文本.Split(new string[] { "\r\n", }, StringSplitOptions.RemoveEmptyEntries);

            // （462，315）编号>1：0:行号,列号;1:行号,列号;
            var regNumber = new Regex(@"^（\d+，\d+）编号>.*[:：]?");

            // （462，315）试题>1：A:*行号,列号;B:~行号,列号;
            var regQues = new Regex(@"（\d+，\d+）试题>.*[:：]?");

            foreach (var line in lines)
            {
                if (line.StartsWith("答题卡宽高比>："))
                {
                    if (double.TryParse(line.Substring("答题卡宽高比>：".Length), out double borderWidthRate))
                    {
                        _答题卡宽高比 = borderWidthRate;
                    }
                }
                else if (line.StartsWith("边框宽度比>："))
                {
                    if (double.TryParse(line.Substring("边框宽度比>：".Length), out double borderWidthRate))
                    {
                        _边框宽度比 = borderWidthRate;
                    }
                }
                else if (line.StartsWith("边框高度比>："))
                {
                    if (double.TryParse(line.Substring("边框高度比>：".Length), out double borderHeightRate))
                    {
                        _边框高度比 = borderHeightRate;
                    }
                }
                else if (line.StartsWith("答题卡列数>："))
                {
                    if (int.TryParse(line.Substring("答题卡列数>：".Length), out int colsCount))
                    {
                        _答题卡列数 = colsCount;
                    }
                }
                else if (line.StartsWith("答题卡行数>："))
                {
                    if (int.TryParse(line.Substring("答题卡行数>：".Length), out int rowsCount))
                    {
                        _答题卡行数 = rowsCount;
                    }
                }
                else if (line.IndexOf("编号>") >= 0)
                {
                    double? width = null;
                    double? height = null;
                    var regSize = new Regex(@"(?<=^（).*?(?=）)");
                    var matchSize = regSize.Match(line);
                    if (matchSize.Success)
                    {
                        var spans = matchSize.Value.Split(new char[] { ',', '，' }, StringSplitOptions.RemoveEmptyEntries);
                        if (spans.Length == 2)
                        {
                            if (double.TryParse(spans[0], out double w))
                            {
                                width = w;
                            }
                            else continue;

                            if (double.TryParse(spans[1], out double h))
                            {
                                height = h;
                            }
                            else continue;
                        }
                    }

                    var index = line.IndexOf("：");
                    if (index < 0) continue;

                    var tail = line.Substring(index + 1);

                    var entry = new 答题卡编号填涂条目(tail, _答题卡列数.Value, _答题卡行数.Value);
                    _编号条目列表.Add(entry);
                }
                else if (line.IndexOf("试题>") >= 0)
                {
                    double? width = null;
                    double? height = null;
                    var regSize = new Regex(@"(?<=^（).*?(?=）)");
                    var matchSize = regSize.Match(line);
                    if (matchSize.Success)
                    {
                        var spans = matchSize.Value.Split(new char[] { ',', '，' }, StringSplitOptions.RemoveEmptyEntries);
                        if (spans.Length == 2)
                        {
                            if (double.TryParse(spans[0], out double w))
                            {
                                width = w;
                            }
                            else continue;

                            if (double.TryParse(spans[1], out double h))
                            {
                                height = h;
                            }
                            else continue;
                        }
                    }

                    var index = line.IndexOf("：");
                    if (index < 0) continue;

                    var numTxt = line.Substring(0, index);
                    var tail = line.Substring(index + 1);
                    var entry = new 答题卡试题填涂条目(numTxt, tail, _答题卡列数.Value, _答题卡行数.Value);

                    _试题条目列表.Add(entry);
                }
            }
        }

        internal 答题卡试题填涂条目 取试题条目(string v)
        {
            if (string.IsNullOrEmpty(v)) return null;
            if (_试题条目列表 == null || _试题条目列表.Count <= 0) return null;

            foreach (var entry in _试题条目列表)
            {
                if (entry.题号 != null && entry.题号.ToLower() == v) return entry;
            }

            return null;
        }

        public bool 读卡时尝试连接外框缺口 { get; set; } = false;
    }

    public class 答题卡选项尺寸
    {
        public 答题卡选项尺寸(double width, double height)
        {
            宽度 = width;
            高度 = height;
        }

        public double 宽度 { get; set; }
        public double 高度 { get; set; }
    }

    public class 答题卡选项坐标
    {
        public int 行号 { get; set; }

        public int 列号 { get; set; }
    }

    /// <summary>
    /// 填涂条目分编号、试题两种。每个填涂条目包括多个填涂选项。
    /// </summary>
    public abstract class 答题卡填涂条目
    {
        public 答题卡填涂条目(string entryText, double w, double h)
        {
            _答案文本 = entryText;
            _填涂选项集.Clear();

            var spansOfOptions = entryText.Split(new char[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
            // 每个成员都是：0:行号,列号;      或者：B:~行号,列号;
            // 前一种是编号（用于考试证号填涂）；后一种是选择题，其中 *~ 用以判断当前项应否填涂。
            var answerTxt = new StringBuilder();
            foreach (var span in spansOfOptions)
            {
                var index = span.IndexOf(":");
                if (index < 0) continue;
                var optionTxt = span.Substring(0, index);
                var txt = span.Substring(index + 1);
                var spans = txt.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
                if (spans.Length != 2) continue;

                bool isAnswer = false;
                if (spans[0].StartsWith("*"))
                {
                    spans[0] = spans[0].Substring(1);
                    answerTxt.Append(optionTxt);
                    isAnswer = true;
                }
                else if (spans[0].StartsWith("~"))
                {
                    spans[0] = spans[0].Substring(1);
                    isAnswer = false;
                }

                if (int.TryParse(spans[0], out int row) == false) continue;
                if (int.TryParse(spans[1], out int col) == false) continue;

                var option = new 答题卡填涂选项(this, w, h, new 答题卡选项坐标() { 行号 = row, 列号 = col, }, optionTxt, isAnswer);
                _填涂选项集.Add(option);
            }
            _答案文本 = answerTxt.ToString();
        }

        private List<答题卡填涂选项> _填涂选项集 = new List<答题卡填涂选项>();
        /// <summary>
        /// 此答题卡填涂条目下的所有选项的集合列表。
        /// </summary>
        public List<答题卡填涂选项> 填涂选项集 { get => _填涂选项集; }

        private string _答案文本 = "";
        /// <summary>
        /// 答题卡定义文本中的选项应有状态。（*表示是答案，应填涂；~表示非答案，应不填涂。）
        /// </summary>
        public string 答案文本 { get => _答案文本; }
    }

    /// <summary>
    /// 这是指某个填涂条目中某个具体的选项（例如：B项）。
    /// </summary>
    public class 答题卡填涂选项
    {
        /// <summary>
        /// 构造方法。
        /// </summary>
        /// <param name="w">答题卡有效宽度。</param>
        /// <param name="h">答题卡有效高度。</param>
        /// <param name="选项坐标"></param>
        public 答题卡填涂选项(答题卡填涂条目 entry, double w, double h, 答题卡选项坐标 选项坐标, string optionTxt, bool isAnswer)
        {
            _所在填涂条目 = entry;
            _答题卡宽度 = w;
            _答题卡高度 = h;
            _坐标 = 选项坐标;
            _选项文本 = optionTxt;
            _是否答案项 = isAnswer;
        }

        private double _答题卡宽度;

        public double 答题卡宽度 { get => _答题卡宽度; }

        private double _答题卡高度;

        public double 答题卡高度 { get => _答题卡高度; }

        private string _选项文本;
        /// <summary>
        /// 选项文本，用于和答案比对，查看当前选项是否被选中。
        /// </summary>
        public string 选项文本 { get => _选项文本; }

        private bool _是否答案项 = false;
        /// <summary>
        /// 这是答题卡定义文件中规定的。
        /// </summary>
        public bool 是否答案项 { get => _是否答案项; }
        /// <summary>
        /// 此属性值与“是否答案顶”一致。
        /// </summary>
        public bool 是否应被填涂 { get => _是否答案项; }

        private bool? _是否被填涂;
        /// <summary>
        /// 此属性在比对时被设置。
        /// 在开始下次比对前，应将此属性设置为 null。
        /// </summary>
        public bool? 是否被填涂
        {
            get { return this._是否被填涂; }
            set { this._是否被填涂 = value; }
        }

        private 答题卡选项坐标 _坐标;
        /// <summary>
        /// 由答题卡定义文本文件解析而来。
        /// </summary>
        public 答题卡选项坐标 坐标 { get => _坐标; }

        private 答题卡填涂条目 _所在填涂条目;

        public 答题卡填涂条目 所在填涂条目 { get => _所在填涂条目; }
        public int 非零像素数量 { get; internal set; }
        public Rect 填涂块矩形 { get; internal set; }

        internal void 置是否答案(bool v)
        {
            this._是否答案项 = v;
        }
    }

    public class 答题卡编号填涂条目 : 答题卡填涂条目
    {
        public 答题卡编号填涂条目(string entryText, double w, double h) : base(entryText, w, h)
        {

        }
    }

    public class 答题卡试题填涂条目 : 答题卡填涂条目
    {
        public 答题卡试题填涂条目(string numTxt, string entryText, double w, double h) : base(entryText, w, h)
        {
            _题号 = numTxt;
        }

        private string _题号;

        public string 题号 { get => _题号; }

        internal void 置答案文本(string qSpan)
        {
            if (string.IsNullOrWhiteSpace(qSpan)) return;
            if (this.填涂选项集 == null || this.填涂选项集.Count <= 0) return;

            foreach (var option in this.填涂选项集)
            {
                option.置是否答案(false);
            }

            foreach (var item in qSpan)
            {
                答题卡填涂选项 destOption = null;
                foreach (var option in this.填涂选项集)
                {
                    if (option.选项文本 != null && option.选项文本.ToLower() == item.ToString().ToLower())
                    {
                        destOption = option;
                    }
                }

                if (destOption != null)
                {
                    destOption.置是否答案(true);
                }
            }
        }
    }
}